'''Represents the current state of the game'''

import game_board
import game_constants
import utility
import location

flowsort = utility.update_to_dataflow(lambda l, cmp=None, key=None: l.sort(cmp=cmp, key=key))


class GameState(object):
    def __init__(self,
                 game_id,
                 can_start,
                 started,
                 game_round,
                 player_turn,
                 registered_players,
                 characters,
                 weapons,
                 game_over,
                 game_winner):
        super(GameState, self).__init__()
        self.game_id = game_id
        self.can_start = can_start
        self.started = started
        self.round = game_round
        self.player_turn = player_turn
        self.players = registered_players
        self.characters = characters
        self.weapons    = weapons
        self.game_over = game_over
        self.game_winner = game_winner

        

    def __repr__(self):
        items = ', '.join('%s: %s' % rec for rec in self.__dict__.iteritems())
        return '(%s)' % items

class PlayerPublic(object):
    def __init__(self, player_id, character_choice):
        super(PlayerPublic, self).__init__()
        self.player_id = player_id
        self.character_choice = character_choice
        self.false_accusation = False

    def __repr__(self):
        items = ', '.join('%s: %s' % rec for rec in self.__dict__.iteritems())
        return '(%s)' % items
        
class PlayerPrivate(object):
    def __init__(self, cards = None):
        super(PlayerPrivate, self).__init__()
        self.cards = cards

    def __repr__(self):
        items = ', '.join('%s: %s' % rec for rec in self.__dict__.iteritems())
        return '(%s)' % items

class Suggestion(object):
    def __init__(self, player_id, character, weapon, room, other_players_gen):
        self.player_id = player_id
        self.character = character
        self.weapon = weapon
        self.room = room
        self.other_players = other_players_gen

class InvalidCharacter( Exception ): pass
class CharacterAlreadyTaken( Exception ): pass
class GameFull( Exception ): pass

class Game(object):
    minimum_players = 3
    maximum_players = 6

    
    def __init__(self, identity, update_notify_cb, update_specific_client_cb):
        self.id = identity
        self.players = dict()
        self.char_to_player_map = dict()
        self.started = False
        self.game_round_counter = utility.Counter()
        self.update_notify_cb = update_notify_cb
        self.update_specific_client_cb = update_specific_client_cb

        self.game_over = False
        self.game_winner = None

        self.turn_order = None
        self.current_round = None
        self.current_turn = None
        self.current_suggestion = None
        
        (self.rooms_pos, self.rooms_name, self.characters, self.weapons, self.deck, self.answer) = game_board.make_house()

        print 'The answer is: %s' % repr(self.answer)

    def query_house(self, player_id):
        self.update_specific_client_cb(self.id, player_id, 'query_house_response', self.rooms_name[ 'Study' ])

    def add_player(self, player, character_choice):
        if character_choice not in game_constants.g_characters:
            raise InvalidCharacter( 'Invalid character choice %s' % chracter_choice )

        if character_choice in self.char_to_player_map:
            raise CharacterAlreadyTaken( 'Character choice already taken: %s' % character_choice )

        if not self.can_add_player():
            raise GameFull()

        #position = self.characters[ character_choice ].location.position
        self.players[ player ] = (PlayerPublic(player, character_choice),
                                  PlayerPrivate())
        self.char_to_player_map[ character_choice ] = player
        self.update_notify()


    def start_game(self):
        # Now that we know # of playesr, decide on play-order
        characters = flowsort(self.char_to_player_map.iteritems(),
                              cmp=game_constants.character_pred,
                              key=lambda x: x[0])
        
        # deal cards amongst players
        num_players = len(self.players)
        for i in range(num_players):

            (char, player) = characters[ i ]
            (pub, priv) = self.players[ player ]
            priv.cards = self.deck[i::num_players]
            
#           print 'Dealing cards to: %s, %s, %s' % (characters[ i ], repr(pub), repr(priv))
            
            # notify each player of his hand
            self.update_specific_client_cb(self.id, pub.player_id, 'deal_cards', priv.cards)

        # update all clients to indicate game has started
        self.started = True
        self.turn_order = utility.Cycle(p for (c,p) in characters)

        # finally notify the next client to take its turn
        # NOTE: willt trigger a general notification
        self.trigger_next_turn()

    def trigger_next_turn(self):
        self.current_turn = self.turn_order.get_next()

        if self.current_turn not in self.players:
            raise Exception('Current turn: %s, is not a valid palyer' % self.current_turn)
            
        (pub,priv) = self.players[self.current_turn]
        if pub.false_accusation:
            self.trigger_next_turn()
        else:
            self.current_round = self.game_round_counter.next_count()
            self.update_specific_client_cb(self.id, pub.player_id, 'take_turn')
            self.update_notify()

        

    def move_player(self, player_id, character, new_position):
        if new_position not in self.rooms_pos:
            raise Exception('Position not valid: %s' % new_position)
        if player_id not in self.players:
            raise Exception('Unknown player: %s' % player_id)

        (pub,priv) = self.players[ player_id ]

        if pub.character_choice != character:
            raise Exception('Player %s trying to move character %s, but is playing as %s' % (player_id, character, pub.character_choice))
        
        new_location = self.rooms_pos[ new_position ]

#        print 'moving to: %s' % repr(new_location)
        
        game_piece = self.characters[ character ]

        location.move_game_piece(game_piece, new_location, False) # TODO: Add move validation
        self.update_notify()
        

    def make_suggestion(self, player_id, sug_character, sug_weapon, sug_room):
        if player_id not in self.players:
            raise Exception('Unknown player: %s' % player_id)

        if sug_character not in self.characters:
            raise Exception('Unknown character: %s' % sug_character)

        if sug_weapon not in self.weapons:
            raise Exception('Unknown weapon: %s' % sug_weapon)

        if sug_room not in self.rooms_name:
            raise Exception('Unknown room: %s' % sug_room)

        (pub, priv) = self.players[ player_id ]
        char_loc = self.characters[ pub.character_choice ].location
        sug_loc  = self.rooms_name[ sug_room ]
        if char_loc is not sug_loc:
            raise Exception( 'Making a suggestion in room %s, but located in %s' % repr(sug_loc), repr(char_loc))

        #todo: verify that player is currently occupying room

        location.move_game_piece(self.characters[ sug_character], sug_loc)
        location.move_game_piece(self.weapons[ sug_weapon ], sug_loc)
        self.current_suggestion = Suggestion(player_id, sug_character, sug_weapon, sug_room,
                                             (p for p in self.players if p != player_id))
        self.try_next_suggestion()
        
    def try_next_suggestion(self):

        if self.current_suggestion is None:
            raise Exception('Not currently handling a suggestion')

        try:
            next_player_id = self.current_suggestion.other_players.next()
            self.update_specific_client_cb(self.id,
                                           next_player_id,
                                           'respond_to_suggestion',
                                           self.current_suggestion.player_id,
                                           self.current_suggestion.character,
                                           self.current_suggestion.weapon,
                                           self.current_suggestion.room)
        except StopIteration:
            self.update_specific_client_cb(self.id, self.current_suggestion.player_id, 'no_suggestion_responses')
            self.current_suggestion = None

    def turn_completed(self, player_id):
        if self.current_turn != player_id:
            raise Exception('Not your turn! %s' % player_id)
        
        self.trigger_next_turn()

    def suggestion_response(self, player_id, dest_player_id, response): 
        if response is None:
            self.try_next_suggestion()

        else:
            # Todo: need to validate response
            self.update_specific_client_cb(self.id, self.current_suggestion.player_id, 'received_suggestion_response', player_id, response)
            self.current_suggestion = None

    def make_accusation(self, player_id, character, weapon, room):
        if (character, weapon, room) == tuple(c.name for c in self.answer):
            self.game_over = True
            self.game_winner = player_id

            for p in self.players:
                self.update_specific_client_cb(self.id, p, 'game_over', player_id, character, weapon, room)
        else:
            (pub,priv) = self.players[player_id]
            pub.false_accusation = True

            for p in self.players:
                self.update_specific_client_cb(self.id, p, 'false_accusation', player_id, character, weapon, room)

        self.update_notify()

    def update_notify(self):
        gs = GameState( self.id,
                        self.enough_players(),
                        self.started,
                        self.current_round,
                        self.current_turn,
                        [pub for (p, (pub,priv)) in self.players.iteritems()],
                        [(c.name, repr(c.location)) for c in self.characters.itervalues()],
                        [(w.name, repr(w.location)) for w in self.weapons.itervalues()],
                        self.game_over,
                        self.game_winner)
        
        self.update_notify_cb( gs )


    def can_add_player(self):
        return len(self.players) < Game.maximum_players

    def enough_players(self):
        return len(self.players) >= Game.minimum_players

    def __hash__(self):
        return self.id.__hash__()

    def __cmp__(self, g1):
        return cmp(self.id, g1.id)

    def __repr__(self):
        return 'Game: %s' % self.id



if __name__ == '__main__':
    pass
